﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>Combines the article filter criteria.</summary>
    /// <threadsafety static="true" instance="false"/>
    public struct InboundArticleFilter : IEquatable<InboundArticleFilter>
    {
        private const int NullValue = 0;
        private const int FalseValue = 1;
        private const int TrueValue = 2;
        private const int ValueCount = 3;

        private static readonly InboundArticleFilter NullFilterField = new InboundArticleFilter();
        private readonly int hashCode;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="InboundArticleFilter"/> struct.</summary>
        public InboundArticleFilter(
            bool? isOriginalBeingWatched,
            bool? hasOriginalBeenRead,
            bool? isFollowupBeingWatched,
            bool? hasFollowupBeenRead)
        {
            this.hashCode =
                (((((ToInt(isOriginalBeingWatched) * ValueCount) + ToInt(hasOriginalBeenRead)) * ValueCount) +
                ToInt(isFollowupBeingWatched)) * ValueCount) + ToInt(hasFollowupBeenRead);
        }

        /// <summary>Gets a filter criterion for the articles returned by
        /// <see cref="IGroup.GetOriginalInboundArticles"/>.</summary>
        /// <value>The value <c>true</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return watched original
        /// articles; <c>false</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return unwatched original
        /// articles; <c>null</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return both watched and
        /// unwatched articles.</value>
        public bool? IsOriginalBeingWatched
        {
            get { return ToNullableBool((this.hashCode / ValueCount / ValueCount / ValueCount) % ValueCount); }
        }

        /// <summary>Gets a filter criterion for the articles returned by
        /// <see cref="IGroup.GetOriginalInboundArticles"/>.</summary>
        /// <value>The value <c>true</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return read original
        /// articles; <c>false</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return unread original
        /// articles; <c>null</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return both read and unread
        /// original articles.</value>
        public bool? HasOriginalBeenRead
        {
            get { return ToNullableBool((this.hashCode / ValueCount / ValueCount) % ValueCount); }
        }

        /// <summary>Gets a filter criterion for the articles returned by
        /// <see cref="IGroup.GetOriginalInboundArticles"/>.</summary>
        /// <value>The value <c>true</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return original articles
        /// with at least one watched follow-up article; <c>false</c> to let
        /// <see cref="IGroup.GetOriginalInboundArticles"/> return original articles with at least one unwatched
        /// follow-up article; <c>null</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return original
        /// articles with either watched or unwatched follow-up articles.</value>
        public bool? IsFollowupBeingWatched
        {
            get { return ToNullableBool((this.hashCode / ValueCount) % ValueCount); }
        }

        /// <summary>Gets a filter criterion for the articles returned by
        /// <see cref="IGroup.GetOriginalInboundArticles"/>.</summary>
        /// <value>The value <c>true</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return original articles
        /// with at least one read follow-up article; <c>false</c> to let
        /// <see cref="IGroup.GetOriginalInboundArticles"/> return original articles with at least one unread follow-up
        /// article; <c>null</c> to let <see cref="IGroup.GetOriginalInboundArticles"/> return original articles with
        /// either read or unread follow-up articles.</value>
        public bool? HasFollowupBeenRead
        {
            get { return ToNullableBool(this.hashCode % ValueCount); }
        }

        /// <summary>See <see cref="IEquatable{T}.Equals"/>.</summary>
        public bool Equals(InboundArticleFilter other)
        {
            return other.hashCode == this.hashCode;
        }

        /// <summary>See <see cref="object.Equals(object)"/>.</summary>
        public override bool Equals(object obj)
        {
            var other = obj as InboundArticleFilter?;
            return other.HasValue && this.Equals(other.Value);
        }

        /// <summary>See <see cref="object.GetHashCode"/>.</summary>
        /// <returns>A value unambiguously identifying the state of this struct.</returns>
        public override int GetHashCode()
        {
            return this.hashCode;
        }

        /// <summary>Gets an <see cref="InboundArticleFilter"/> object that does not filter any articles.</summary>
        public static InboundArticleFilter NullFilter
        {
            get { return NullFilterField; }
        }

        /// <summary>Determines whether two specified instances of <see cref="InboundArticleFilter"/> are equal.
        /// </summary>
        public static bool operator ==(InboundArticleFilter left, InboundArticleFilter right)
        {
            return left.Equals(right);
        }

        /// <summary>Determines whether two specified instances of <see cref="InboundArticleFilter"/> are not equal.
        /// </summary>
        public static bool operator !=(InboundArticleFilter left, InboundArticleFilter right)
        {
            return !left.Equals(right);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static int ToInt(bool? value)
        {
            switch (value)
            {
                case null:
                    return NullValue;
                case false:
                    return FalseValue;
                default:
                    return TrueValue;
            }
        }

        private static bool? ToNullableBool(int value)
        {
            switch (value)
            {
                case NullValue:
                    return null;
                case FalseValue:
                    return false;
                default:
                    return true;
            }
        }
    }
}
